Skip to content

Conversation

@julienrbrt
Copy link
Member

Closes: #2965

@github-actions
Copy link
Contributor

github-actions bot commented Jan 15, 2026

The latest Buf updates on your PR. Results from workflow CI / buf-check (pull_request).

BuildFormatLintBreakingUpdated (UTC)
✅ passed⏩ skipped✅ passed❌ failed (1)Jan 22, 2026, 9:43 AM

@claude
Copy link
Contributor

claude bot commented Jan 15, 2026

Claude finished @julienrbrt's task in 4m 57s —— View job


Comprehensive Code Review

I've completed a thorough review of this PR. Here are my findings organized by category:


1. Architecture & Design ✅

Strengths:

  • Clean separation of concerns: The new GetExecutionInfo() and FilterTxs() methods appropriately separate execution parameter retrieval from transaction processing
  • Well-designed interface evolution: The core Executor interface now focuses solely on state management (InitChain, ExecuteTxs return only state roots), while execution parameters are accessed via dedicated methods
  • Proper abstraction layers: gRPC client/server correctly implement the new interface, maintaining clean boundaries between transport and business logic

Concerns:

  • Protobuf breaking change (Buf check failing): The max_bytes field was removed from ExecuteTxsResponse (proto/evnode/v1/execution.proto:79). This is a wire-format breaking change that will break existing clients.

    Options to resolve:

    1. Recommended: Deprecate instead of remove - Keep the field but mark it deprecated and stop populating it:
      // Deprecated: Use GetExecutionInfo() to retrieve execution parameters
      uint64 max_bytes = 2 [deprecated = true];
    2. Version the API - Create v2 of the ExecutorService if backward compatibility is critical
    3. Accept the breaking change - Document it prominently in CHANGELOG.md with migration guide if this is pre-1.0 and breaking changes are acceptable

2. Performance & Resource Management ⚠️

Critical Performance Concern - Unmarshaling overhead:

In execution/evm/execution.go:858-876, the PR introduces transaction unmarshaling when hasForceIncludedTransaction is true:

if hasForceIncludedTransaction {
    var ethTx types.Transaction
    if err := ethTx.UnmarshalBinary(tx); err != nil {
        // ... filter out invalid tx
    }
    txGas = ethTx.Gas()
}

Analysis of performance impact (addressing tac0turtle's concern):

  • Previous behavior: Only force-included txs were unmarshaled
  • New behavior: ALL txs (force-included + mempool) are unmarshaled when any force-included tx is present
  • Frequency: This occurs once per DA epoch (not every block), as noted in julienrbrt's response
  • Impact: One block per DA epoch will have higher CPU usage due to unmarshaling all transactions

Will this increase block time? (tac0turtle's follow-up question)

  • Unlikely to cause issues in normal operation:
    • Transaction unmarshaling in go-ethereum is relatively fast (< 1ms per tx for typical txs)
    • This only happens once per DA epoch (e.g., every 10-50 blocks)
    • Most blocks won't have force-included txs and will skip unmarshaling entirely
  • However, edge cases could be problematic:
    • DA epoch with 1000+ transactions could add 100ms+ of unmarshaling time
    • If block time is tight (e.g., 1-2 seconds), this could cause slight delays

Recommendations:

  1. Add benchmark tests to quantify the performance impact with realistic workloads (100, 500, 1000 txs)
  2. Consider optimization: Track which txs are force-included so you only unmarshal those:
    // Pass force-included tx count to FilterTxs, unmarshal only first N txs
  3. Monitor in production: Add histogram metric for FilterTxs execution time when hasForceIncludedTransaction=true
  4. Document the tradeoff in code comments

Caching optimization ✅:

  • The cachedExecutionInfo atomic pointer (execution/evm/execution.go:182) is a good optimization to avoid repeated RPC calls for gas limits
  • Cache invalidation issue: Cache is never invalidated. If gas limit changes (e.g., via governance), stale value will be used. Consider:
    • Invalidating cache on SetFinal()
    • Adding TTL (cache for N blocks)
    • Making gas limit updates explicit events

3. Concurrency & Thread Safety ✅

Good practices observed:

  • Proper use of atomic.Pointer for caching execution info (execution/evm/execution.go:182)
  • Mutex protection for shared state in EngineClient (blockHashCache, currentHeadBlockHash)
  • Context propagation throughout all methods

Minor concern:

  • In pkg/sequencers/single/sequencer.go:227, calling executor.GetExecutionInfo() without timeout:
    info, err := c.executor.GetExecutionInfo(ctx)
    If the execution client is slow/unresponsive, this could block the sequencer indefinitely. Consider adding a timeout context:
    ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
    defer cancel()
    info, err := c.executor.GetExecutionInfo(ctx)

4. Error Handling & Security ✅

Strengths:

  • Proper error wrapping with context throughout
  • Graceful degradation when GetExecutionInfo() fails (logs warning, proceeds with 0 gas limit)
  • Validation of force-included transactions with appropriate filtering (FilterRemove for invalid)
  • Good use of debug logging for transaction filtering

Concerns:

  • Gibberish transaction logging (execution/evm/execution.go:861-865): Consider rate-limiting this debug log to prevent log spam if an attacker submits many invalid force-included txs to the DA layer. While debug logs are typically disabled in production, this could still fill disk in debug mode.
  • gRPC error codes (execution/grpc/server.go): Good use of connect.CodeInvalidArgument vs connect.CodeInternal, but consider adding more specific error types for better client debugging

Security note ✅:

  • Force-included transaction validation is properly isolated - invalid txs are filtered without affecting the block
  • No injection vulnerabilities in tx handling (uses binary marshaling, not string parsing)

5. Test Coverage & Quality ⚠️

Coverage regression: 58.51% → 57.94% (-0.57%)

Files with low/no coverage (from Codecov report):

  1. pkg/telemetry/executor_tracing.go: 16.66% (40 missing lines) - NEW FILE, wrapper code, acceptable
  2. core/execution/dummy.go: 14.28% (24 missing lines) - test utility, low priority
  3. execution/grpc/client.go: 14.28% (24 missing lines) - NEEDS TESTS
  4. execution/grpc/server.go: 10.52% (17 missing lines) - NEEDS TESTS

Test weaknesses identified:

  1. execution/grpc/server_test.go:177 - Gemini correctly identified that transaction content validation was removed:

    // Before: Checked that returned txs matched expectedTxs
    // After: Only checks len(resp.Msg.Txs)

    This weakens the test and should be restored.

  2. Missing test coverage for new methods:

    • No tests for GetExecutionInfo() caching behavior
    • Limited tests for FilterTxs() with various combinations
    • No tests verifying FilterStatus transitions (OK → Postpone → Remove)
  3. Deleted test file: execution/evm/force_included_test.go was removed (162 lines). Ensure this functionality is covered elsewhere.

Recommendations:

  1. Add gRPC client/server tests for new methods:

    func TestClient_GetExecutionInfo(t *testing.T) { ... }
    func TestClient_FilterTxs(t *testing.T) { ... }
    func TestServer_GetExecutionInfo(t *testing.T) { ... }
    func TestServer_FilterTxs(t *testing.T) { ... }
  2. Add comprehensive FilterTxs test suite:

    • Gas limit enforcement
    • Size limit enforcement
    • Mixed valid/invalid force-included txs
    • Cumulative gas/size tracking
    • Edge cases: empty txs, all postponed, gas overflow
  3. Restore transaction content validation in server_test.go:177

  4. Add integration tests showing sequencer → executor → FilterTxs flow


6. Code Quality & Go Idioms ✅

Good practices:

  • Consistent error handling patterns
  • Proper context usage throughout
  • Clear function documentation with parameter/return value descriptions
  • Idiomatic Go code structure
  • Good use of constants for magic numbers (e.g., maxCacheSize = 10)

Minor improvements:

  1. Consistent naming: hasForceIncludedTransaction (bool parameter) vs FilterStatus (enum) - consider hasForceIncluded for brevity
  2. Error messages: Good context in errors, helpful for debugging

Code duplication:

  • sequencers/single and sequencers/based have similar FilterTxs handling logic. Consider extracting common patterns into sequencers/common if this grows.

7. Documentation & Developer Experience ✅

Strengths:

  • CHANGELOG.md updated with new methods (lines 21-22)
  • Protobuf definitions well-documented with clear comments
  • Interface methods have comprehensive doc comments explaining requirements and behavior
  • Code comments explain complex logic (e.g., checkpoint recovery, transaction filtering)

Missing:

  1. Migration guide for the breaking protobuf change
  2. Performance considerations for FilterTxs (when unmarshaling occurs)
  3. Examples of using the new interface methods
  4. Protobuf changelog entry noting the breaking change

Recommendation: Add to CHANGELOG.md:

### Breaking Changes
- **BREAKING**: Removed `max_bytes` field from `ExecuteTxsResponse` in protobuf API. 
  Clients should now call `GetExecutionInfo()` to retrieve execution parameters. 
  Migration: Update clients to call the new `GetExecutionInfo` RPC method instead of 
  relying on the `max_bytes` return value from `ExecuteTxs`.

8. Specific Issues to Address

High Priority (blocking):

  1. Buf breaking change failure - Must resolve before merge
    • Option A: Deprecate field instead of removing
    • Option B: Accept breaking change and document migration
  2. ⚠️ Performance benchmarking - Quantify unmarshaling overhead to answer tac0turtle's concern
  3. ⚠️ Test coverage for new gRPC methods - Current coverage too low

Medium Priority (should fix):

  1. Cache invalidation strategy for cachedExecutionInfo
  2. Timeout handling - Add timeout to GetExecutionInfo() calls in sequencers
  3. Restore test validation in server_test.go:177

Low Priority (nice to have):

  1. Log rate limiting for invalid force-included txs
  2. Migration guide in CHANGELOG.md
  3. Extract common sequencer logic to reduce duplication

Summary

This is a well-architected refactoring that meaningfully improves the separation of concerns in the execution layer. The changes enable proper gas-aware filtering for force-included transactions, which is essential for the forced inclusion feature.

Strengths:

  • ✅ Clean interface design
  • ✅ Proper error handling and graceful degradation
  • ✅ Good documentation in code comments
  • ✅ Consistent implementation across all executors (EVM, gRPC, testapp)

Main risks:

  • Breaking protobuf change without migration path (Buf check failing)
  • ⚠️ Performance impact from unmarshaling all txs when force-included txs present (needs measurement)
  • ⚠️ Test coverage regression for critical new functionality
  • ⚠️ Cache invalidation missing for execution info

Overall assessment: The architectural direction is correct, but there are some implementation gaps that should be addressed before merge.

Recommendation: ✅ Request changes

  • Must fix: Buf breaking change
  • Should fix: Add benchmarks, improve test coverage, add timeouts
  • Nice to have: Cache invalidation, migration docs

Action Items for Author

Priority fixes before merge:

  • Resolve Buf breaking change - Deprecate max_bytes field or explicitly accept breaking change with docs
  • Add benchmark tests for FilterTxs to quantify performance impact (address tac0turtle's concern)
  • Add unit tests for GetExecutionInfo and FilterTxs in gRPC client/server
  • Restore transaction content validation in server_test.go:177

Recommended improvements:

  • Add timeout to GetExecutionInfo calls in sequencers (5s timeout)
  • Implement cache invalidation for cachedExecutionInfo (TTL or event-based)
  • Add migration guide to CHANGELOG.md for breaking change
  • Consider rate limiting for invalid force-included tx debug logs

Nice-to-have:

  • Extract common FilterTxs logic in sequencers to reduce duplication
  • Add integration test demonstrating end-to-end force-included tx flow
  • Document performance characteristics in FilterTxs docstring

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @julienrbrt, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly refactors the interaction between ev-node and its execution clients. By removing the maxBytes return value from the core InitChain and ExecuteTxs methods, it introduces more specialized interfaces (ExecutionInfoProvider and DATransactionFilter). This change enhances modularity, allowing for dynamic retrieval of execution parameters and robust, gas-aware filtering of transactions originating from the Data Availability layer, thereby improving the system's flexibility and maintainability.

Highlights

  • Refactored Executor Interface: The core Executor interface's InitChain and ExecuteTxs methods no longer directly return maxBytes or gasLimit, streamlining their primary responsibility to state root management.
  • Introduced ExecutionInfoProvider: A new ExecutionInfoProvider interface has been added, allowing execution clients to expose dynamic parameters, such as MaxGas, through a dedicated GetExecutionInfo method. This provides a more flexible way to retrieve execution-layer specific information.
  • Introduced DATransactionFilter: A new DATransactionFilter interface is now available for execution clients to implement. This interface provides a FilterDATransactions method, specifically designed to validate and filter force-included transactions from the Data Availability (DA) layer based on execution-layer rules, such as gas limits.
  • EVM Engine Client Enhancements: The EVM EngineClient has been updated to implement both the new ExecutionInfoProvider and DATransactionFilter interfaces. It now retrieves gas limits dynamically and performs gas-aware filtering of DA transactions before execution.
  • Codebase-Wide Signature Updates: Numerous calls to InitChain and ExecuteTxs across the entire codebase, including application logic, tests, and mock implementations, have been updated to align with the revised function signatures, removing the maxBytes return value.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the Executor interface by removing the maxBytes return value from InitChain and ExecuteTxs. It introduces new interfaces, ExecutionInfoProvider and DATransactionFilter, to expose execution client parameters like MaxGas more explicitly. The changes are applied consistently throughout the codebase, including implementations, mocks, and tests. This is a solid refactoring that improves the separation of concerns. I've found one issue in a test file where a check was removed, weakening the test's validation.

Comment on lines 175 to 177
if len(resp.Msg.Txs) != len(expectedTxs) {
t.Fatalf("expected %d txs, got %d", len(expectedTxs), len(resp.Msg.Txs))
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The check for the content of the returned transactions has been removed, which weakens the test. It now only verifies the number of transactions, not that they are the correct ones. It would be better to verify the content as well to ensure the server is returning the expected data.

if len(resp.Msg.Txs) != len(expectedTxs) {
	t.Fatalf("expected %d txs, got %d", len(expectedTxs), len(resp.Msg.Txs))
}
for i, tx := range resp.Msg.Txs {
	if string(tx) != string(expectedTxs[i]) {
		t.Errorf("tx %d: expected %s, got %s", i, expectedTxs[i], tx)
	}
}

@codecov
Copy link

codecov bot commented Jan 15, 2026

Codecov Report

❌ Patch coverage is 48.19277% with 129 lines in your changes missing coverage. Please review.
✅ Project coverage is 57.89%. Comparing base (9348732) to head (53a6de9).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
pkg/telemetry/executor_tracing.go 16.66% 40 Missing ⚠️
core/execution/dummy.go 14.28% 24 Missing ⚠️
execution/grpc/client.go 14.28% 24 Missing ⚠️
execution/grpc/server.go 10.52% 17 Missing ⚠️
pkg/sequencers/single/sequencer.go 85.48% 5 Missing and 4 partials ⚠️
pkg/sequencers/based/sequencer.go 79.48% 6 Missing and 2 partials ⚠️
block/internal/syncing/syncer.go 66.66% 3 Missing and 4 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2982      +/-   ##
==========================================
- Coverage   58.63%   57.89%   -0.75%     
==========================================
  Files         111      110       -1     
  Lines       10405    10526     +121     
==========================================
- Hits         6101     6094       -7     
- Misses       3659     3781     +122     
- Partials      645      651       +6     
Flag Coverage Δ
combined 57.89% <48.19%> (-0.75%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Base automatically changed from julien/da-strategy to main January 16, 2026 12:24
@julienrbrt julienrbrt marked this pull request as ready for review January 21, 2026 23:48
@julienrbrt julienrbrt requested review from alpe and tac0turtle January 22, 2026 08:40
// Mempool txs are already validated, so we can skip parsing when not needed
if hasForceIncludedTransaction {
var ethTx types.Transaction
if err := ethTx.UnmarshalBinary(tx); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

have you dont benchmarks on this flow to verify and document latency increases?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, because we just have to (https://github.com/evstack/ev-node/pull/2982/changes/BASE..edbdae99cbd768bcb10bf80a2286d7e3a8643769#diff-73d38fc0f126d3d764a9855162c561f8a2cfe9595029aa13f510ab5937885d2dL821).

The difference in this PR is that when there's a force tx we unmarshal all txs, while before we would unmarshal only force txs. In theory, given we process force txs per epoch (so not every blocks), this just mean 1 block with more cpu cycles used every da epoch.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will that one block cause the block time to increase?

Copy link
Member Author

@julienrbrt julienrbrt Jan 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me think how to benchmark this. I'll try to add something.
I think it could only when a chain is really congested with many big txs via mempool and 1+ tx on DA.
Because the duplicate work is only for the mempool one. But it is probably negligible.

Copy link
Contributor

@tac0turtle tac0turtle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good, we will need to benchmark and understand how this effects fast chains.

@julienrbrt julienrbrt added this pull request to the merge queue Jan 22, 2026
Merged via the queue into main with commit 555a48d Jan 22, 2026
36 of 38 checks passed
@julienrbrt julienrbrt deleted the julien/gas branch January 22, 2026 10:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Expose gas in execution interface

3 participants